import socket
import requests
import json
import logging
import os
from ruamel.yaml import YAML # 导入 ruamel.yaml

# --- 配置 ---
UDP_IP = "127.0.0.1"  # 监听的IP地址
UDP_PORT = 12345      # 监听的端口号
HTTP_BASE_URL = "http://192.168.31.146:9000/shoe/type/getShoeParam"
PARAM_YAML_PATH = "param.yaml" # 本地 YAML 文件路径
# --- 配置结束 ---

# 设置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

def fetch_params_http(code, image_name, is_left_foot):
    url = f"{HTTP_BASE_URL}?code={code}&image={image_name}"
    try:
        logger.info(f"Fetching parameters from {url}")
        response = requests.get(url)
        response.raise_for_status() # 检查 HTTP 错误
        data = response.json()

        if data.get("code") != 200:
            logger.error(f"HTTP API returned error code: {data.get('code')}, msg: {data.get('msg')}")
            return None

        api_data = data.get("data", {})
        side_key = "left" if is_left_foot else "right"
        regional_key = f"{side_key}Regional"
        offset_key = f"{side_key}Offset"

        spray_params_str = api_data.get(side_key)
        regional_info_str = api_data.get(regional_key)
        offset_info = api_data.get(offset_key, {})

        if not spray_params_str or not regional_info_str:
            logger.error(f"Missing '{side_key}' or '{regional_key}' data in API response.")
            return None

        try:
            spray_params = json.loads(spray_params_str)
            regional_info = json.loads(regional_info_str)
        except json.JSONDecodeError as e:
            logger.error(f"Failed to decode JSON for {side_key} or {regional_key}: {e}")
            return None

        return {
            "spray_params": spray_params,
            "regional_info": regional_info,
            "offset_info": offset_info
        }
    except requests.exceptions.RequestException as e:
        logger.error(f"Request failed: {e}")
        return None
    except Exception as e:
        logger.error(f"An unexpected error occurred while fetching parameters: {e}")
        return None

def update_yaml_config(filepath, is_left_foot, api_response_data):
    """
    读取、更新并写回 YAML 配置文件，保留注释和格式。
    Args:
        filepath (str): YAML 文件路径。
        is_left_foot (bool): True 为左脚, False 为右脚。
        api_response_data (dict): 从 HTTP API 获取并解析的数据。
    """
    yaml = YAML() # 创建 YAML 实例
    yaml.preserve_quotes = True  # 尝试保留引号

    try:
        with open(filepath, 'r', encoding='utf-8') as file:
            config_data = yaml.load(file)
        
        if config_data is None: # 如果文件为空
            config_data = {}
            logger.warning(f"YAML file {filepath} was empty. Created an empty structure.")
        
        logger.info(f"Loaded YAML configuration from {filepath}")

        try:
            outer_spray_config = config_data["algorithm_parameters"]["outer_spray_offset"]
        except KeyError as e:
            logger.error(f"YAML structure missing expected key: {e}. Cannot update parameters.")
            return

        outer_spray_config["is_left_foot"] = is_left_foot

        regional_info = api_response_data["regional_info"]
        for i in range(1, 6): # 5个区域
            key = f"x_region_percent_{i}"
            if i - 1 < len(regional_info):
                proportion = regional_info[i - 1].get("proportion", 0.0)
                percentage = proportion * 100.0
                outer_spray_config[key] = percentage
                logger.debug(f"Updated {key} to {percentage}% based on API proportion {proportion}")
            else:
                logger.warning(f"API regional_info has fewer than {i} elements. Keeping original value for {key}.")

        spray_params = api_response_data["spray_params"]
        if spray_params:
            for i in range(8):
                point_data = spray_params[i] if i < len(spray_params) else spray_params[-1] if spray_params else {"x": 0, "y": 0, "z": 0}

                z_value_from_spray_point = point_data.get("x", 0) # API 的 x 对应喷射高度
                z_key = f"z_offset_region_{i+1}"
                outer_spray_config[z_key] = z_value_from_spray_point

                y_value_from_spray_point = point_data.get("y", 0) # API 的 y 对应喷胶距离
                y_key = f"normal_offset_region_{i+1}"
                outer_spray_config[y_key] = y_value_from_spray_point

                angle_value_from_spray_point = point_data.get("z", 0) # API 的 z 对应喷射角度
                angle_key = f"axis_angle_{i+1}"
                outer_spray_config[angle_key] = angle_value_from_spray_point

                logger.debug(f"Updated region {i+1}: {z_key}={z_value_from_spray_point}, {y_key}={y_value_from_spray_point}, {angle_key}={angle_value_from_spray_point}")

            if len(spray_params) < 8:
                 logger.info(f"API spray_params has only {len(spray_params)} points, less than 8. Last point's values will be used for remaining regions.")

        else:
            logger.warning("API spray_params is empty. Skipping z_offset, normal_offset, and axis_angle updates.")


        x_offset_from_api = float(api_response_data["offset_info"].get("x", 0))
        for i in range(1, 9): # 8个区域
            key = f"x_offset_region_{i}"
            outer_spray_config[key] = x_offset_from_api

        logger.info(f"YAML configuration's outer_spray_offset section updated for {'left' if is_left_foot else 'right'} foot.")

        with open(filepath, 'w', encoding='utf-8') as file:
            yaml.dump(config_data, file)
        logger.info(f"Configuration saved back to {filepath}")

    except Exception as e:
        logger.error(f"Error reading/writing YAML file {filepath}: {e}")


def main():
    """
    主函数，设置 UDP Socket 并处理消息。
    """
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind((UDP_IP, UDP_PORT))
    logger.info(f"UDP Server listening on {UDP_IP}:{UDP_PORT}")

    try:
        while True:
            data, addr = sock.recvfrom(1024) # 缓冲区大小为 1024 字节
            message = data.decode('utf-8').strip()
            logger.info(f"Received message: '{message}' from {addr}")

            try:
                parts = message.split(',')
                if len(parts) != 3:
                    raise ValueError("Message format should be 'left_or_right,code,image_name'")
                
                left_or_right_str, code, image_name = parts
                left_or_right_str = left_or_right_str.lower()

                if left_or_right_str == "left":
                    is_left = True
                elif left_or_right_str == "right":
                    is_left = False
                else:
                    raise ValueError(f"Invalid foot type '{left_or_right_str}'. Expected 'left' or 'right'.")

            except ValueError as e:
                logger.warning(f"Invalid message format received: '{message}'. Error: {e}. Expected 'left_or_right,code,image_name'. Ignoring.")
                continue # 忽略无效消息，继续监听

            api_data = fetch_params_http(code, image_name, is_left)
            if not api_data:
                logger.error("Failed to fetch parameters from HTTP API. Skipping YAML update.")
                continue

            update_yaml_config(PARAM_YAML_PATH, is_left, api_data)

    except KeyboardInterrupt:
        logger.info("Received interrupt signal. Shutting down...")
    except Exception as e:
        logger.error(f"An unexpected error occurred in main loop: {e}")
    finally:
        sock.close()
        logger.info("UDP Socket closed.")

if __name__ == "__main__":
    main()